package com.aooci.ordercat.service.impl;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.ResourceBundle;

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.aooci.ordercat.dao.OrdercatDAO;
import com.aooci.ordercat.dao.OrderedItemDAO;
import com.aooci.ordercat.dao.RecetteDAO;
import com.aooci.ordercat.domain.Recette;
import com.aooci.ordercat.enums.OrderType;
import com.aooci.ordercat.enums.OrderedItemType;
import com.aooci.ordercat.service.PrintingService;
import com.aooci.ordercat.vo.OperatedOrderVo;
import com.aooci.ordercat.vo.OrderedItemVo;

@Service("printingService")  
@Transactional
public class PrintingServiceImpl implements PrintingService, Serializable {
	private static final long serialVersionUID = -5978004761103388486L;
	
	private static final int PRINTER_CONNECTION_TIMEOUT = 10000;
	//private static final int PRINTER_LINE_SPACING_KITCHEN = 80;
	//private static final int PRINTER_LINE_SPACING_RECEIPT = 60;
	private static final int DEFAULT_PRINTER_LINE_WIDTH = 41;
	private static final String RECEIPT_FORMAT_1 = "%1$3d  %2$-18S  %3$7.2f  %4$7.2f";
	private static final String RECEIPT_FORMAT_2 = "                %1$15s %2$9.2f";
	private static final String RECEIPT_FORMAT_3 = "%1$4d     %2$9.2f  %3$9.2f   %4$9.2f";
	
	private Socket kitchenPrinter;
	private PrintWriter kitchenPrinterWriter;
	
	private String kitchenPrinterIP;
	private int kitchenPrinterPort;
	private int kitchenPrinterSpacing;
	private String kitchenPrinterCharacterSet;
	//private int kitchenPrinterLineWidth;
	
	private Socket receiptPrinter;
	private PrintWriter receiptPrinterWriter;
	
	private String receiptPrinterIP;
	private int receiptPrinterPort;
	private int receiptPrinterSpacing;
	private String receiptPrinterCharacterSet;
	//private int receiptPrinterLineWidth;


	/**
	 * @return the receiptPrinterCharacterSet
	 */
	public String getReceiptPrinterCharacterSet() {
		return receiptPrinterCharacterSet;
	}

	/**
	 * @param receiptPrinterCharacterSet the receiptPrinterCharacterSet to set
	 */
	public void setReceiptPrinterCharacterSet(String receiptPrinterCharacterSet) {
		this.receiptPrinterCharacterSet = receiptPrinterCharacterSet;
	}

	/**
	 * @return the kitchenPrinterSpacing
	 */
	public int getKitchenPrinterSpacing() {
		return kitchenPrinterSpacing;
	}

	/**
	 * @param kitchenPrinterSpacing the kitchenPrinterSpacing to set
	 */
	public void setKitchenPrinterSpacing(int kitchenPrinterSpacing) {
		this.kitchenPrinterSpacing = kitchenPrinterSpacing;
	}

	/**
	 * @return the receiptPrinterSpacing
	 */
	public int getReceiptPrinterSpacing() {
		return receiptPrinterSpacing;
	}

	/**
	 * @param receiptPrinterSpacing the receiptPrinterSpacing to set
	 */
	public void setReceiptPrinterSpacing(int receiptPrinterSpacing) {
		this.receiptPrinterSpacing = receiptPrinterSpacing;
	}

	/**
	 * @param receiptPrinterWriter the receiptPrinterWriter to set
	 */
	public void setReceiptPrinterWriter(PrintWriter receiptPrinterWriter) {
		this.receiptPrinterWriter = receiptPrinterWriter;
	}

	/**
	 * @param receiptPrinterIP the receiptPrinterIP to set
	 */
	public void setReceiptPrinterIP(String receiptPrinterIP) {
		this.receiptPrinterIP = receiptPrinterIP;
	}

	/**
	 * @param receiptPrinterPort the receiptPrinterPort to set
	 */
	public void setReceiptPrinterPort(int receiptPrinterPort) {
		this.receiptPrinterPort = receiptPrinterPort;
	}

	/**
	 * @param kitchenPrinterIP the kitchenPrinterIP to set
	 */
	public void setKitchenPrinterIP(String kitchenPrinterIP) {
		this.kitchenPrinterIP = kitchenPrinterIP;
	}

	/**
	 * @param kitchenPrinterPort the kitchenPrinterPort to set
	 */
	public void setKitchenPrinterPort(int kitchenPrinterPort) {
		this.kitchenPrinterPort = kitchenPrinterPort;
	}

	/**
	 * @param kitchenPrinterCharacterSet the kitchenPrinterCharacterSet to set
	 */
	public void setKitchenPrinterCharacterSet(String kitchenPrinterCharacterSet) {
		this.kitchenPrinterCharacterSet = kitchenPrinterCharacterSet;
	}
	
	private OrdercatDAO ordercatDAO;
	public void setOrdercatDAO(OrdercatDAO ordercatDAO) {
		this.ordercatDAO = ordercatDAO;
	}
	
	private OrderedItemDAO orderedItemDAO;
	public void setOrderedItemDAO(OrderedItemDAO orderedItemDAO) {
		this.orderedItemDAO = orderedItemDAO;
	}
	
	private RecetteDAO recetteDAO;
	/**
	 * @param recetteDAO the recetteDAO to set
	 */
	public void setRecetteDAO(RecetteDAO recetteDAO) {
		this.recetteDAO = recetteDAO;
	}
	
	public void initialize() throws IOException{

	}
	
	
	private void initPrinter(PrintWriter printWriter) throws IOException{
		printWriter.write(0x1B);
	    printWriter.write(0x40);
	}
	
	private void initKitchenPrinterWriter() throws IOException{
		this.kitchenPrinter = new Socket(); 
		this.kitchenPrinter.connect(new InetSocketAddress(this.kitchenPrinterIP, this.kitchenPrinterPort), PRINTER_CONNECTION_TIMEOUT);
	    this.kitchenPrinterWriter = new PrintWriter(new OutputStreamWriter(kitchenPrinter.getOutputStream(), this.kitchenPrinterCharacterSet), false);
	    this.initPrinter(this.kitchenPrinterWriter);
	}
	
	private void closeKitchenPrinterWriter() throws IOException{
		this.kitchenPrinterWriter.close();
		this.kitchenPrinter.close();
	}
	
	private void initReceiptPrinterWriter() throws IOException{
	    this.receiptPrinter = new Socket(); 
	    this.receiptPrinter.connect(new InetSocketAddress(this.receiptPrinterIP, this.receiptPrinterPort), PRINTER_CONNECTION_TIMEOUT);
	    this.receiptPrinterWriter = new PrintWriter(new OutputStreamWriter(receiptPrinter.getOutputStream(), this.receiptPrinterCharacterSet), false);
	    this.initPrinter(this.receiptPrinterWriter);
	}
	
	private void closeReceiptPrinterWriter() throws IOException{
		this.receiptPrinter.close();
		this.receiptPrinterWriter.close();
	}
	
//	private void enableChineseCharacters(PrintWriter printWriter){
//		printWriter.write(0x1C);
//		printWriter.write(0x26);
//	}
//	
//	private void feedPaper(PrintWriter printWriter){
//		printWriter.write(0x1B);
//		printWriter.write(0x4A);
//		printWriter.write(100);
//	}
	
	private void cutPaper(PrintWriter printWriter){
		printWriter.write(0x1D);
		printWriter.write(0x56);
		printWriter.write(66);
		printWriter.write(0);
	}
	
	private void setPrinterToNormalWidth(PrintWriter printWriter){
		printWriter.write(0x1B);
		printWriter.write(0x21);
		printWriter.write(0x00);
	}
	
	private void setPrinterToDoubleWidth(PrintWriter printWriter){
		printWriter.write(0x1B);
		printWriter.write(0x21);
		printWriter.write(0x20);
	}
	
	private void enableUnderlineMode(PrintWriter printWriter){
		printWriter.write(0x1B);
		printWriter.write(0x2D);
		printWriter.write(0x1);
	}
	
	private void disableNoUnderlineMode(PrintWriter printWriter){
		printWriter.write(0x1B);
		printWriter.write(0x2D);
		printWriter.write(0x00);
	}
	
	private void printWithDoubleWidth(PrintWriter printWriter, Object o){
		this.setPrinterToDoubleWidth(printWriter);
		printWriter.print(o);
		this.setPrinterToNormalWidth(printWriter);
	}
	
	private void setLineSpacing(PrintWriter printWriter, int spacing){
		printWriter.write(0x1B);
		printWriter.write(0x33);
		printWriter.write(spacing);
	}
	
	private void printSingleLine(PrintWriter printWriter){
		for(int i=0;i<DEFAULT_PRINTER_LINE_WIDTH;i++){
			printWriter.print("_");
		}
		printWriter.print("\n");
	}
	
	private void printDoubleLine(PrintWriter printWriter){
		for(int i=0;i<DEFAULT_PRINTER_LINE_WIDTH;i++){
			printWriter.print("=");
		}
		printWriter.print("\n");
	}

	@Override
	@Async
	public void printToKitchen(OperatedOrderVo operatedOrderVo) {
		try {
			this.initKitchenPrinterWriter();
			SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:MM");
			this.setPrinterToNormalWidth(this.kitchenPrinterWriter);
			this.setLineSpacing(this.kitchenPrinterWriter, this.kitchenPrinterSpacing);
			if(operatedOrderVo.getOrdercat().getPrintCount() > 0){
				this.kitchenPrinterWriter.println(">>>>>>>>>>>>>>> Modification <<<<<<<<<<<<<<<");
			}else{
				this.printDoubleLine(this.kitchenPrinterWriter);
			}
			
			this.kitchenPrinterWriter.println(operatedOrderVo.getOrdercat().getType() + "                 " + dateFormat.format(operatedOrderVo.getOrdercat().getCreationTimestamp().getTime()));
			if(OrderType.DINING_HALL.equals(operatedOrderVo.getOrdercat().getType())){
				this.printWithDoubleWidth(this.kitchenPrinterWriter, "Table:" + operatedOrderVo.getDiningTable().getLabel() + "          Client:" + operatedOrderVo.getDiningTable().getNofClient() + "\n");
			}else if(OrderType.TAKEAWAY.equals(operatedOrderVo.getOrdercat().getType())){
				this.printWithDoubleWidth(this.kitchenPrinterWriter, dateFormat.format(operatedOrderVo.getTakeaway().getTakingTimestamp().getTime()) + "\n");
			}

			this.printSingleLine(this.kitchenPrinterWriter);
			//List<OrderedItemVo> orderedItems = new ArrayList<OrderedItemVo>();
			boolean switcher = false;
			for(int sortGroupId:this.orderedItemDAO.selectSortGroupByOrdercatId(operatedOrderVo.getOrdercat().getId())){
//				SortGroup sortGroup = this.sortGroupDAO.findById(sortGroupId);
//				if(sortGroup.isPrintKitchen()){
					for(OrderedItemVo orderedItemVo: this.orderedItemDAO.selectByOrdercatIdAndSortGroup(operatedOrderVo.getOrdercat().getId(), sortGroupId)){
						if(orderedItemVo.getItem().isPrintKitchen()){
							if(switcher){
								orderedItemVo.setFirst(true);
								this.printSingleLine(this.kitchenPrinterWriter);
								switcher = false;
							}
	
							if((operatedOrderVo.getOrdercat().getPrintCount() > 0) && (orderedItemVo.getModifiedCount() > 0)){
								this.kitchenPrinterWriter.print("[+"+orderedItemVo.getModifiedCount()+"] ");
							}else if((operatedOrderVo.getOrdercat().getPrintCount() > 0) && (orderedItemVo.getModifiedCount() < 0)){
								this.kitchenPrinterWriter.print("["+orderedItemVo.getModifiedCount()+"] ");
							}else if((orderedItemVo.getRemark() != null) && (!orderedItemVo.getRemark().isEmpty())){
								this.kitchenPrinterWriter.print("[*] ");
							}
							this.printWithDoubleWidth(this.kitchenPrinterWriter, orderedItemVo.getCount() + " X " + orderedItemVo.getItem().getLabel());
							
							if((orderedItemVo.getRemark() != null) && (!orderedItemVo.getRemark().isEmpty())){
								this.kitchenPrinterWriter.print(" (" + orderedItemVo.getRemark()+")\n");
							}else{
								this.kitchenPrinterWriter.print("\n");
							}
						}
					}
					switcher = true;
				//}
				this.setPrinterToNormalWidth(this.kitchenPrinterWriter);
			}
			this.printDoubleLine(this.kitchenPrinterWriter);
			//this.feedPaper();
			this.cutPaper(this.kitchenPrinterWriter);
			this.kitchenPrinterWriter.flush();
			this.closeKitchenPrinterWriter();
			operatedOrderVo.getOrdercat().setPrintCount(operatedOrderVo.getOrdercat().getPrintCount() + 1);
			this.ordercatDAO.save(operatedOrderVo.getOrdercat());
			this.orderedItemDAO.resetModifiedCountByOrdercatId(operatedOrderVo.getOrdercat().getId());
			operatedOrderVo.setHasAddingKitchenItems(this.orderedItemDAO.hasAddingKitchenItems(operatedOrderVo.getOrdercat().getId()));
			operatedOrderVo.setHasKitchenItems(this.orderedItemDAO.hasKitchenItems(operatedOrderVo.getOrdercat().getId()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	@Override
	@Async
	public void printAddingToKitchen(OperatedOrderVo operatedOrderVo) {
		try {
			this.initKitchenPrinterWriter();
			SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:MM");
			this.setPrinterToNormalWidth(this.kitchenPrinterWriter);
			this.setLineSpacing(this.kitchenPrinterWriter, this.kitchenPrinterSpacing);

			this.printDoubleLine(this.kitchenPrinterWriter);
			this.kitchenPrinterWriter.println(operatedOrderVo.getOrdercat().getType() + "                 " + dateFormat.format(operatedOrderVo.getOrdercat().getCreationTimestamp().getTime()));
			if(OrderType.DINING_HALL.equals(operatedOrderVo.getOrdercat().getType())){
				this.printWithDoubleWidth(this.kitchenPrinterWriter, "Table:" + operatedOrderVo.getDiningTable().getLabel() + "          Client:" + operatedOrderVo.getDiningTable().getNofClient() + "\n");
			}else if(OrderType.TAKEAWAY.equals(operatedOrderVo.getOrdercat().getType())){
				this.printWithDoubleWidth(this.kitchenPrinterWriter, dateFormat.format(operatedOrderVo.getTakeaway().getTakingTimestamp().getTime()) + "\n");
			}

			this.printSingleLine(this.kitchenPrinterWriter);
			boolean switcher = false;
			for(int sortGroupId:this.orderedItemDAO.selectSortGroupByOrdercatId(operatedOrderVo.getOrdercat().getId())){
//				SortGroup sortGroup = this.sortGroupDAO.findById(sortGroupId);
//				if(sortGroup.isPrintKitchen()){
					for(OrderedItemVo orderedItemVo: this.orderedItemDAO.selectByOrdercatIdAndSortGroup(operatedOrderVo.getOrdercat().getId(), sortGroupId)){
						if(orderedItemVo.getItem().isPrintKitchen()){
							if(switcher){
								orderedItemVo.setFirst(true);
								this.printSingleLine(this.kitchenPrinterWriter);
								switcher = false;
							}
	
							if(orderedItemVo.getModifiedCount() > 0){
								this.printWithDoubleWidth(this.kitchenPrinterWriter, orderedItemVo.getModifiedCount() + " X " + orderedItemVo.getItem().getLabel());
								
								if((orderedItemVo.getRemark() != null) && (!orderedItemVo.getRemark().isEmpty())){
									this.kitchenPrinterWriter.print(" (" + orderedItemVo.getRemark()+")\n");
								}else{
									this.kitchenPrinterWriter.print("\n");
								}
							}
						}	
					}
					switcher = true;
				//}
				this.setPrinterToNormalWidth(this.kitchenPrinterWriter);
			}
			this.printDoubleLine(this.kitchenPrinterWriter);
			//this.feedPaper();
			this.cutPaper(this.kitchenPrinterWriter);
			this.kitchenPrinterWriter.flush();
			this.closeKitchenPrinterWriter();
			operatedOrderVo.getOrdercat().setPrintCount(operatedOrderVo.getOrdercat().getPrintCount() + 1);
			this.ordercatDAO.save(operatedOrderVo.getOrdercat());
			this.orderedItemDAO.resetModifiedCountByOrdercatId(operatedOrderVo.getOrdercat().getId());
			operatedOrderVo.setHasAddingKitchenItems(this.orderedItemDAO.hasAddingKitchenItems(operatedOrderVo.getOrdercat().getId()));
			operatedOrderVo.setHasKitchenItems(this.orderedItemDAO.hasKitchenItems(operatedOrderVo.getOrdercat().getId()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void printOrderedItems(OperatedOrderVo operatedOrderVo, Recette recette, ResourceBundle messageBundle){
		this.enableUnderlineMode(this.receiptPrinterWriter);
		this.receiptPrinterWriter.println("Qu.  Item                   U.P.     Prix");
		this.disableNoUnderlineMode(this.receiptPrinterWriter);
		for(int sortGroupId:this.orderedItemDAO.selectSortGroupByOrdercatId(operatedOrderVo.getOrdercat().getId())){
			for(OrderedItemVo orderedItemVo: this.orderedItemDAO.selectByOrdercatIdAndSortGroup(operatedOrderVo.getOrdercat().getId(), sortGroupId)){
				if(OrderedItemType.NORMAL.equals(orderedItemVo.getType()) || OrderedItemType.HOSTED.equals(orderedItemVo.getType())){
					String itemName = messageBundle.getString("item." + orderedItemVo.getItem().getLabel());
					this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_1, orderedItemVo.getCount(), itemName.length() > 18 ? itemName.substring(0, 15)+"..." : itemName, orderedItemVo.getItem().getUnitPrice(), orderedItemVo.getTotalPrice()));
				}
			}
		}
		this.printSingleLine(this.receiptPrinterWriter);
		
		
		
		if(recette.getOffert() > 0){
			this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_2, "Offert:", (recette.getOffert()*-1)));
		}
		if(recette.getDiscountRate() > 0){
			this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_2, "Discount(-"+recette.getDiscountRate()+"%):", (recette.getDiscount()*-1)));
		}
		
		this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_2, "Total TVAC:", recette.getFinalTotal()));
	}

	@Override
	public void printReceiptToClient(OperatedOrderVo operatedOrderVo, ResourceBundle messageBundle) {
		try {
			this.initReceiptPrinterWriter();
			SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HH:MM");
			this.setPrinterToNormalWidth(this.receiptPrinterWriter);
			this.setLineSpacing(this.receiptPrinterWriter, this.receiptPrinterSpacing);
			Recette recette = this.recetteDAO.findByOrdercatId(operatedOrderVo.getOrdercat().getId());
			this.receiptPrinterWriter.println("La Fontaine de Jade");
			this.receiptPrinterWriter.println("Avenue de Tervuren, 5");
			this.receiptPrinterWriter.println("1040 Etterbeek");
			this.printDoubleLine(this.receiptPrinterWriter);
			this.receiptPrinterWriter.print(operatedOrderVo.getOrdercat().getType());
			
			if(OrderType.DINING_HALL.equals(operatedOrderVo.getOrdercat().getType())){
				this.receiptPrinterWriter.print("     " + operatedOrderVo.getDiningTable().getLabel());
			}
			this.receiptPrinterWriter.print("\n");
			this.printOrderedItems(operatedOrderVo, recette, messageBundle);

			this.printDoubleLine(this.receiptPrinterWriter);
			this.receiptPrinterWriter.println("            Merci de votre visit");
			this.receiptPrinterWriter.println();
			this.cutPaper(this.receiptPrinterWriter);
			//==============================================================================================
			this.receiptPrinterWriter.println("Backup Ticket for Waiter");
			this.receiptPrinterWriter.println("Order ID:" + operatedOrderVo.getOrdercat().getId());
			this.receiptPrinterWriter.println(operatedOrderVo.getOrdercat().getType() + "          " + dateFormat.format(operatedOrderVo.getOrdercat().getCreationTimestamp().getTime()));
			this.printDoubleLine(this.receiptPrinterWriter);
			this.printOrderedItems(operatedOrderVo, recette, messageBundle);
			this.receiptPrinterWriter.println();

			DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.US);
			otherSymbols.setDecimalSeparator('.');
			otherSymbols.setGroupingSeparator(','); 
			DecimalFormat df =new DecimalFormat("#.00", otherSymbols);
			
			this.printDoubleLine(this.receiptPrinterWriter);
			this.enableUnderlineMode(this.receiptPrinterWriter);
			this.receiptPrinterWriter.println("%TVA          HTVA        TVA        TVAC");
			this.disableNoUnderlineMode(this.receiptPrinterWriter);
			if(recette.getTva6() > 0){
				float htva = Float.valueOf(df.format(recette.getTva6()/1.06));
				float tva = Float.valueOf(df.format(recette.getTva6() - htva));
				this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_3, 6, htva, tva, recette.getTva6()));
			}
			if(recette.getTva12() > 0){
				float htva = Float.valueOf(df.format(recette.getTva12()/1.12));
				float tva = Float.valueOf(df.format(recette.getTva12() - htva));
				this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_3, 12, htva, tva, recette.getTva12()));
			}
			if(recette.getTva21() > 0){
				float htva = Float.valueOf(df.format(recette.getTva21()/1.21));
				float tva = Float.valueOf(df.format(recette.getTva21() - htva));
				this.receiptPrinterWriter.println(String.format(RECEIPT_FORMAT_3, 21, htva, tva, recette.getTva21()));
			}
			this.receiptPrinterWriter.println();
			//this.receiptPrinterWriter.println();
			this.cutPaper(this.receiptPrinterWriter);
			this.receiptPrinterWriter.flush();
			this.closeReceiptPrinterWriter();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}
